Stanford AI has developed a dataset of cars with make, model and year. The aim of this project is to classify cars as accurately as possible using a convolutional neural network. We will use the Keras package with Tensorflow backend to run model training, and we will validate and evaluate the accuracy of the model based on the parameters.
Model Aim: Classify a car as part of the 196 classes in the dataset in terms of both make and model year.
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import os
import json
import plotly.graph_objs as go
from scipy import stats
from plotly.tools import FigureFactory as FF
from plotly import offline
from plotly.offline import iplot
from keras.models import Sequential
from keras.models import model_from_json
from keras.layers import Conv2D
from keras.layers import MaxPool2D
from keras.layers import Dropout
from keras.layers import Flatten
from keras.layers import Dense
from keras.optimizers import SGD
from keras.preprocessing import image
from keras.preprocessing.image import ImageDataGenerator
# For each car, find the number of images (both training and testing sets)
import os
root_dir = "../scraped_images_2020/train"
file_dict = {}
for dir_, _, files in os.walk(root_dir):
rel_dir = os.path.relpath(dir_, root_dir)
file_dict.setdefault(rel_dir, len(files))
file_dict.pop('.')
0
test_image1 = image.load_img('../scraped_images_2020/train/acura_rlx_hybrid_2020/1c248b948d.jpg', target_size=(150, 225))
test_image2 = image.load_img('../scraped_images_2020/train/acura_rlx_hybrid_2020/1f7729757b.jpg', target_size=(150, 225))
car_max_name = max(file_dict, key=file_dict.get)
car_max_num_images = file_dict[car_max_name]
car_min_name = min(file_dict, key=file_dict.get)
car_min_num_images = file_dict[car_min_name]
img_count_list = list(file_dict.values())
mean_num_images = np.mean(img_count_list)
print(f"{car_max_name} has the most number of images with {car_max_num_images}")
print(f"The average `number of images per car is {round(mean_num_images, 2)}")
acura_mdx_awd_2020 has the most number of images with 268 The average `number of images per car is 64.58
make_list = np.unique([x.split("_")[0] for x in file_dict.keys()])
make_count = {k: 0 for k in make_list}
for model, count in file_dict.items():
make = model.split('_')[0]
make_count[make] += count
stats.describe(img_count_list)
DescribeResult(nobs=616, minmax=(0, 268), mean=64.58279220779221, variance=790.9232156055326, skewness=-1.0272165326748641, kurtosis=5.655287380425829)
make_count_df = pd.DataFrame({"Make": list(make_count.keys()), "Count": list(make_count.values())})
make_count_df.sort_values('Count', inplace=True, ascending=True)
make_count_df
| Make | Count | |
|---|---|---|
| 45 | train | 0 |
| 42 | test | 0 |
| 39 | scraped | 0 |
| 48 | xse | 62 |
| 36 | pro | 62 |
| 2 | aston | 72 |
| 7 | byd | 77 |
| 40 | se | 112 |
| 34 | platinum | 118 |
| 43 | touring | 145 |
| 27 | lotus | 148 |
| 13 | fiat | 150 |
| 4 | bentley | 215 |
| 11 | dodge | 228 |
| 10 | chrysler | 229 |
| 1 | alfa | 237 |
| 23 | lamborghini | 290 |
| 29 | mazda | 303 |
| 37 | ram | 305 |
| 38 | rolls-royce | 422 |
| 15 | genesis | 435 |
| 46 | volkswagen | 475 |
| 0 | acura | 501 |
| 12 | ferrari | 524 |
| 8 | cadillac | 538 |
| 6 | buick | 541 |
| 32 | mitsubishi | 596 |
| 19 | infiniti | 617 |
| 28 | maserati | 677 |
| 26 | lincoln | 684 |
| 41 | subaru | 730 |
| 17 | honda | 749 |
| 3 | audi | 761 |
| 24 | land | 978 |
| 16 | gmc | 1080 |
| 31 | mini | 1203 |
| 18 | hyundai | 1218 |
| 21 | jeep | 1221 |
| 33 | nissan | 1226 |
| 47 | volvo | 1332 |
| 22 | kia | 1363 |
| 20 | jaguar | 1809 |
| 35 | porsche | 1820 |
| 25 | lexus | 1979 |
| 9 | chevrolet | 1999 |
| 44 | toyota | 2047 |
| 14 | ford | 2124 |
| 30 | mercedes-benz | 3457 |
| 5 | bmw | 3924 |
fig = go.Figure(go.Bar(
x=list(make_count_df["Count"]),
y=list(make_count_df["Make"]),
orientation='h'))
fig.update_layout(title='Car Maufacturers With the Most Images',
xaxis_title='Count',
yaxis_title='Make')
fig.show()
We will use 3 convolution layers for the sake of computing power, with all layers using the relu activation function. We use this function because of it's non-linear (compared to sigmoid, for example, which can cause neurons to 'vanish').
config = {'img_pixels': 256,
'n_filters': 64,
'layer_nodes': 512,
'batchsize': 32,
'epochs': 10,
'kernel_size': (4,4),
'pool_size': (2,2),
'dropout':0.2,
'steps_per_epoch': 10,
'validation_steps': 5
}
# config
img_pixels = config['img_pixels']
n_filters = config['n_filters']
layer_nodes = config['layer_nodes']
batchsize = config['batchsize']
epochs = config['epochs']
kernel_size = config['kernel_size']
pool_size = config['pool_size']
dropout = config['dropout']
steps_per_epoch = config['steps_per_epoch']
validation_steps = config['validation_steps']
folders = 0
fs = 0
for _, dirnames, filenames in os.walk("../scraped_images_2020/train"):
# ^ this idiom means "we won't be using this value"
folders += len(dirnames)
fs += len(filenames)
folders
616
fs
39783
car_classifier = Sequential()
#Adding 1st Convolution and Pooling Layer
car_classifier.add(Conv2D(n_filters,kernel_size=kernel_size,input_shape=(img_pixels,img_pixels,3),activation='relu'))
car_classifier.add(MaxPool2D(pool_size=pool_size))
car_classifier.add(Dropout(dropout))
#Adding 2nd Convolution and Pooling Layer
car_classifier.add(Conv2D(n_filters,kernel_size=kernel_size,activation='relu'))
car_classifier.add(MaxPool2D(pool_size=pool_size))
car_classifier.add(Dropout(dropout))
#Adding 3rd Convolution and Pooling Layer
car_classifier.add(Conv2D(n_filters,kernel_size=kernel_size,activation='relu'))
car_classifier.add(MaxPool2D(pool_size=pool_size))
car_classifier.add(Dropout(dropout))
#Adding 4th Convolution and Pooling Layer
car_classifier.add(Conv2D(n_filters,kernel_size=kernel_size,activation='relu'))
car_classifier.add(MaxPool2D(pool_size=pool_size))
car_classifier.add(Dropout(dropout))
#Adding 5th Convolution and Pooling Layer
car_classifier.add(Conv2D(n_filters,kernel_size=kernel_size,activation='relu'))
car_classifier.add(MaxPool2D(pool_size=pool_size))
car_classifier.add(Dropout(dropout))
#Flatten
car_classifier.add(Flatten())
#Adding Input and Output Layer
car_classifier.add(Dense(units=layer_nodes,activation='relu'))
car_classifier.add(Dense(units=layer_nodes,activation='relu'))
car_classifier.add(Dense(units=layer_nodes,activation='relu'))
car_classifier.add(Dense(units=folders,activation='softmax'))
sgd = SGD(lr=0.01, clipvalue=0.5)
car_classifier.compile(optimizer = 'sgd', loss = 'categorical_crossentropy', metrics = ['accuracy'])
/mnt/c/Users/himi6/Google Drive/All documents/python_environments/trading/lib/python3.8/site-packages/tensorflow/python/keras/optimizer_v2/optimizer_v2.py:374: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
# Data agumentation
train_datagen = ImageDataGenerator(rescale=1./255,shear_range=0.2,zoom_range=0.2,horizontal_flip=True)
test_datagen = ImageDataGenerator(rescale=1./255)
train_data = train_datagen.flow_from_directory('../scraped_images_2020/train',
target_size=(img_pixels,img_pixels),
batch_size=batchsize,
class_mode='categorical',
shuffle=True,
seed=42)
test_data = test_datagen.flow_from_directory('../scraped_images_2020/test',
target_size=(img_pixels,img_pixels),
batch_size=1,
class_mode='categorical',
shuffle=True,
seed=42)
Found 39783 images belonging to 616 classes. Found 12577 images belonging to 616 classes.
history = car_classifier.fit_generator(train_data,
steps_per_epoch=steps_per_epoch,
epochs=epochs,
validation_data=test_data,
validation_steps=validation_steps
)
/mnt/c/Users/himi6/Google Drive/All documents/python_environments/trading/lib/python3.8/site-packages/keras/engine/training.py:1915: UserWarning: `Model.fit_generator` is deprecated and will be removed in a future version. Please use `Model.fit`, which supports generators.
Epoch 1/10 10/10 [==============================] - 159s 12s/step - loss: 6.4176 - accuracy: 0.0000e+00 - val_loss: 6.4143 - val_accuracy: 0.0000e+00 Epoch 2/10 10/10 [==============================] - 121s 12s/step - loss: 6.4180 - accuracy: 0.0124 - val_loss: 6.4242 - val_accuracy: 0.0000e+00 Epoch 3/10 10/10 [==============================] - 116s 11s/step - loss: 6.4278 - accuracy: 0.0000e+00 - val_loss: 6.4334 - val_accuracy: 0.0000e+00 Epoch 4/10 10/10 [==============================] - 102s 10s/step - loss: 6.4213 - accuracy: 0.0000e+00 - val_loss: 6.4322 - val_accuracy: 0.0000e+00 Epoch 5/10 10/10 [==============================] - 103s 10s/step - loss: 6.4206 - accuracy: 0.0000e+00 - val_loss: 6.4238 - val_accuracy: 0.0000e+00 Epoch 6/10 10/10 [==============================] - 100s 10s/step - loss: 6.4293 - accuracy: 0.0000e+00 - val_loss: 6.4360 - val_accuracy: 0.0000e+00 Epoch 7/10 10/10 [==============================] - 100s 10s/step - loss: 6.4277 - accuracy: 0.0043 - val_loss: 6.4251 - val_accuracy: 0.0000e+00 Epoch 8/10 10/10 [==============================] - 85s 8s/step - loss: 6.4236 - accuracy: 0.0058 - val_loss: 6.4075 - val_accuracy: 0.0000e+00 Epoch 9/10 10/10 [==============================] - 85s 8s/step - loss: 6.4224 - accuracy: 0.0000e+00 - val_loss: 6.4270 - val_accuracy: 0.0000e+00 Epoch 10/10 10/10 [==============================] - 84s 8s/step - loss: 6.4259 - accuracy: 0.0034 - val_loss: 6.4231 - val_accuracy: 0.0000e+00
metrics = pd.DataFrame.from_dict(history.history)
metrics['epoch'] = metrics.index + 1
metrics
| loss | accuracy | val_loss | val_accuracy | epoch | |
|---|---|---|---|---|---|
| 0 | 6.421993 | 0.000000 | 6.414285 | 0.0 | 1 |
| 1 | 6.420068 | 0.009375 | 6.424158 | 0.0 | 2 |
| 2 | 6.424361 | 0.000000 | 6.433399 | 0.0 | 3 |
| 3 | 6.421460 | 0.000000 | 6.432208 | 0.0 | 4 |
| 4 | 6.421320 | 0.000000 | 6.423793 | 0.0 | 5 |
| 5 | 6.426859 | 0.000000 | 6.435979 | 0.0 | 6 |
| 6 | 6.423881 | 0.003125 | 6.425090 | 0.0 | 7 |
| 7 | 6.422709 | 0.003125 | 6.407460 | 0.0 | 8 |
| 8 | 6.423599 | 0.000000 | 6.426953 | 0.0 | 9 |
| 9 | 6.424075 | 0.003125 | 6.423139 | 0.0 | 10 |
Keep a record of this model for future reference
# serialize model to JSON
model_json = car_classifier.to_json()
with open("../models/cars_classifier_tuned_100eP_50ba_1ba(val).json", "w") as json_file:
json_file.write(model_json)
# serialize weights to HDF5
car_classifier.save_weights("../models/cars_classifier_tuned_100eP_50ba_1ba(val).h5")
print("Saved model to disk")
Saved model to disk
# Save metrics
metrics.to_csv("../models/cars_classifier_metrics2.csv", index=False)
# load json and create model
json_file = open(f"../models/cars_classifier_tuned_100eP_50ba_1ba(val).json", 'r')
loaded_model_json = json_file.read()
json_file.close()
loaded_model = model_from_json(loaded_model_json)
# load weights into new model
loaded_model.load_weights(f"../models/cars_classifier_tuned_100eP_50ba_1ba(val).h5")
print("Loaded model from disk")
Loaded model from disk
# load image
test_image = image.load_img('bmw330i.jpg', target_size=(img_pixels, img_pixels))
test_image
# Convert to array and expand_dims because we are only doing 1 image prediction
test_image_array = image.img_to_array(test_image)
test_image_expand = np.expand_dims(test_image_array, axis=0)
classes = car_classifier.predict(test_image_expand, batch_size=1)
results = {}
iterator = 0
for key in train_data.class_indices:
results.setdefault(key, classes[0][iterator])
iterator+=1
pd.DataFrame.from_dict(results, orient='index').sort_values(0, ascending=False).head(10)
| 0 | |
|---|---|
| infiniti_q60_red_sport_awd_2020 | 0.554902 |
| jaguar_f-type_svr_awd_convertible_2020 | 0.082763 |
| mercedes-benz_sl450_2020 | 0.048177 |
| chevrolet_tahoe_k1500_4wd_2020 | 0.046681 |
| maserati_levante_gts_2020 | 0.031196 |
| xse_2020 | 0.025100 |
| nissan_pathfinder_4wd_2020 | 0.023984 |
| nissan_maxima_2020 | 0.019897 |
| toyota_prius_c_2020 | 0.011394 |
| toyota_86_2020 | 0.011303 |
results = pd.read_csv("../models/cars_classifier_metrics.csv")
results.drop('epochs', axis=1, inplace=True)
results.rename(columns={'Unnamed: 0': 'epochs'}, inplace=True)
results.head()
| epochs | val_loss | val_acc | loss | acc | |
|---|---|---|---|---|---|
| 0 | 0 | 5.262903 | 0.010400 | 5.279155 | 0.005632 |
| 1 | 1 | 5.170683 | 0.013360 | 5.202109 | 0.008648 |
| 2 | 2 | 5.142595 | 0.011800 | 5.143253 | 0.010056 |
| 3 | 3 | 5.115322 | 0.017409 | 5.115203 | 0.012671 |
| 4 | 4 | 5.052454 | 0.016802 | 5.062903 | 0.016383 |
random_x = results['epochs']
random_y0 = results['acc']
random_y1 = results['val_acc']
# Create traces
trace0 = go.Scatter(
x = random_x,
y = random_y0,
mode = 'lines',
name = 'Training Accuracy'
)
trace1 = go.Scatter(
x = random_x,
y = random_y1,
mode = 'lines',
name = 'Validation Accuracy'
)
layout = go.Layout(
title=go.layout.Title(
text='Training vs Validation Accuracy',
xref='paper',
x=0
),
xaxis=go.layout.XAxis(
title=go.layout.xaxis.Title(
text='Epochs',
font=dict(
family='Courier New, monospace',
size=18,
color='#7f7f7f'
)
)
),
yaxis=go.layout.YAxis(
title=go.layout.yaxis.Title(
text='Accuracy',
font=dict(
family='Courier New, monospace',
size=18,
color='#7f7f7f'
)
)
)
)
data = [trace0, trace1]
acc_fig = go.Figure(data=data, layout=layout)
iplot(acc_fig, filename='line-mode')
random_x = results['epochs']
random_y0 = results['loss']
random_y1 = results['val_loss']
# Create traces
trace0 = go.Scatter(
x = random_x,
y = random_y0,
mode = 'lines',
name = 'Training Loss'
)
trace1 = go.Scatter(
x = random_x,
y = random_y1,
mode = 'lines',
name = 'Validation Loss'
)
layout = go.Layout(
title=go.layout.Title(
text='Training vs Validation Loss',
xref='paper',
x=0
),
xaxis=go.layout.XAxis(
title=go.layout.xaxis.Title(
text='Epochs',
font=dict(
family='Courier New, monospace',
size=18,
color='#7f7f7f'
)
)
),
yaxis=go.layout.YAxis(
title=go.layout.yaxis.Title(
text='Loss',
font=dict(
family='Courier New, monospace',
size=18,
color='#7f7f7f'
)
)
)
)
data = [trace0, trace1]
loss_fig = go.Figure(data=data, layout=layout)
iplot(loss_fig, filename='line-mode')